#
# fusion.http.Value
#
# Copyright 2007 Helsinki Institute for Information Technology
# and the authors.
#
# Authors: Ken Rimey <rimey@hiit.fi>
#

# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

'''
Handle URLs of the form "/collections?/([0-9a-f]{40})/(.+)".

Without parameters, GET simply returns unadorned data.  It is useful
for both browsing and programmatic access.
'''

# XXX Guess MIME types?

import logging
import hashlib
from cgi import parse_qs

import web
import simplejson

from fusion.db.db import bytes_to_hex, decode_text
from fusion.http.util import (
    withdb, read_form, get_fields,
    authorized_referer, unauthorized, created)
from fusion.db.exceptions import *

class Value(object):

    @withdb
    def GET(self, db, collection, request_path=''):
        input = web.input(output='', timeout='')

        timeout = None
        block = True
        if input.timeout:
            try:
                timeout = float(input.timeout)
                block = (timeout > 0)
            except ValueError:
                return web.badrequest()

        try:
            value = db.get(collection, request_path,
                           autoimport = True,
                           block = block,
                           timeout = timeout)
        except NoSuchCollection:
            return web.notfound()

        if value is None:
            return web.notfound()

        if input.output == '':
            pass
        elif input.output == 'octet-stream':
            web.header('Content-Type', 'application/octet-stream')
        elif input.output == 'text':
            value = value if decode_text(value) else ''
            web.header('Content-Type', 'text/plain; charset=UTF-8')
        elif input.output == 'hex':
            value = bytes_to_hex(value)
            web.header('Content-Type', 'text/plain')
        elif input.output == 'sha1':
            value = hashlib.sha1(value).digest()
            web.header('Content-Type', 'application/octet-stream')
        elif input.output == 'sha1-hex':
            value = hashlib.sha1(value).hexdigest()
            web.header('Content-Type', 'text/plain')
        else:
            return web.badrequest()

        web.header('Content-Length', str(len(value)))
        web.output(value)

    def POST(self, *args):
        method = web.ctx.env.get('HTTP_X_HTTP_METHOD_OVERRIDE')
        method = method or 'POST'
        if method in ('POST', 'PUT'):
            return self.post_or_put(method, *args)
        else:
            return web.badrequest()

    def PUT(self, *args):
        return self.post_or_put('PUT', *args)

    @withdb
    def post_or_put(self, db, method, collection, path=''):
        if not authorized_referer(collection):
            return unauthorized()

        content_type = web.ctx.env.get('CONTENT_TYPE', '').split(';')[0]
        if not content_type \
                or content_type == 'application/x-www-form-urlencoded':
            # Rejecting the default content type forces clients to
            # specify it explicity.
            return web.badrequest()
        elif content_type == 'multipart/form-data':
            form = read_form()
        else:
            form = None

        try:
            if method == 'PUT' and form is None:
                db.put(collection, path, web.data())
            elif method == 'POST' and form is None:
                path = db.post(collection, path, web.data())
            elif method == 'POST' and form is not None:
                process_form(form, db, collection, path)
            else:
                raise ValueError
        except Unauthorized:
            return unauthorized()
        except NoSuchCollection:
            return web.notfound()

        params = parse_qs(web.ctx.query[1:])
        if 'next' in params:
            url = params['next'][0]
            return web.seeother(url)
        else:
            url = '/collection/%s/%s' % (collection, path)
            return created(url.encode('utf-8'))

def process_form(form, db, collection, path=''):
    view = db.get_view(collection, path)
    json_data = {}
    for field_name in form:
        if ':' in field_name:
            op, path = field_name.split(':', 1)
            for field in get_fields(form, field_name):
                if op in 'put':
                    v = field.value
                elif op == 'put_filename':
                    if not field.filename:
                        continue
                    v = field.filename
                    db.put(collection, 'file/' + v, field.value)
                elif op == 'put_file_under_name':
                    if not field.filename:
                        continue
                    v = field.value
                    path += '/' + field.filename
                else:
                    logging.warning('Unknown op in form: %s' % op)
                    continue

                if '/-/' not in path:
                    view[path] = v
                else:
                    path, rest = path.split('/-/', 1)
                    d = json_data.setdefault(path, {})
                    if v:
                        insert_in_hierarchy(d, rest, v)
        else:
            logging.warning('Ignoring field: %s' % field_name)

    for path in json_data:
        d = clean(json_data[path])
        v = simplejson.dumps(d, sort_keys=True, indent=2)
        view[path] = v

def insert_in_hierarchy(parent, k, v):
    if isinstance(parent, dict):
        if '/' not in k:
            parent[k] = v
        else:
            k, rest = k.split('/', 1)
            child = parent.setdefault(k, {})
            insert_in_hierarchy(child, rest, v)

def clean(data):
    if not isinstance(data, dict):
        return data

    result = {}
    for k, v in data.iteritems():
        v = clean(v)
        if v:
            result[k] = v

    for k in result.keys():
        if k.endswith('_default'):
            base, suffix = k.rsplit('_', 1)
            if not result.get(base):
                result[base] = result[k]

            del result[k]

    empty = all(k.startswith('_') for k in result)
    return {} if empty else result
